<template>
  <div
    class="draft-editor bg-white flex"
    :data-has-draft="!!(draftContent || hasAttachments)"
  >
    <!-- 左侧头像列 -->
    <div v-if="showAvatar" class="login-user mr-3">
      <UserAvatar :image-url="loginUserInfo.avatar" />
    </div>

    <!-- 右侧主体 -->
    <div
      ref="inputWrapperRef"
      :class="[
        'right-main flex-1 flex flex-col px-3 rounded relative w-full',
        'border border-gray-200 border-solid box-border hover:border-gray-300',
      ]"
    >
      <!-- 输入框 -->
      <div
        ref="draftEditorInputRef"
        class="draft-editor--input relative my-3 box-border overflow-y-auto"
        :contentEditable="visible"
        style="white-space: pre-wrap; word-break: break-word"
        @click="onClickContent"
        @keydown.capture="onKeyDown"
        @keyup.capture="onKeyup"
        @mouseup.capture="onMouseUp"
      ></div>

      <!-- 校验信息 -->
      <div
        v-show="!draftContent"
        class="draft-placeholder pointer-events-none absolute teld-color-lightgray mt-3"
      >
        {{ placeholder || qLangComment.commentPlaceholder }}
      </div>

      <div class="error-info teld-color-red relative min-h-5">
        <div v-show="isInputEmpty" class="input-is-empty">
          {{ /** 请输入 */ qLangComment.pleaseInput }}
        </div>
        <div v-show="isInputOverflow" class="input-is-overflow">
          {{ /*内容超出文字数上限*/ qLangComment.inputTooLong }}
        </div>
        <div
          class="draft-content-counting absolute right-0 top-0"
          :class="[isInputOverflow ? 'teld-color-red' : 'teld-color-lightgray']"
        >
          {{ draftWordLength }}/{{ inputMaxLength }}
        </div>
      </div>

      <!-- 附件 -->
      <div
        v-show="fileList.length"
        class="file-list-preview flex flex-col w-full"
      >
        <div
          v-for="(fileItem, fileIndex) in fileList"
          :key="fileItem.uid"
          class="file-item w-full relative p-2 flex items-center hover:teld-bg-color-gray rounded"
        >
          <!-- 文件类型图标 -->
          <FileTypeIcon
            :file-name="fileItem.file && fileItem.file.name"
            class="w-6 h-6 mr-2"
          />
          <!-- 文件名 -->
          <div
            :title="fileItem.file && fileItem.file.name"
            class="flex-1 mr-2 truncate text-xs overflow-hidden w-full"
          >
            {{ fileItem.file && fileItem.file.name }}
          </div>
          <!-- hover时显示的操作按钮 -->
          <div class="hidden flex hover-to-show">
            <!-- 预览 -->
            <t-icon
              v-if="isSupportedImageType(fileItem.file && fileItem.file.type)"
              name="preview"
              class="cursor-pointer mr-2 hover:text-theme-color"
              :title="qLangComment.preview"
              @click.capture.stop="onClickAttachmentPreview(fileItem)"
            ></t-icon>
            <!-- 移除 -->
            <t-icon
              name="close"
              :title="qLangComment.remove"
              class="cursor-pointer hover:text-theme-color"
              @click.stop="handleRemoveFile(fileIndex)"
            />
          </div>

          <!-- loading -->
          <t-inner-loading :showing="fileItem.status === 'ready'">
            <t-spinner size="20px" color="primary" />
          </t-inner-loading>
        </div>
      </div>

      <!-- 底部按钮行 -->
      <div
        class="operation-wrap flex justify-between items-center py-3 border-t border-solid border-gray-100"
      >
        <div class="left teld-theme-color pl-1 flex">
          <!-- @提及人员 -->
          <div
            class="icon-container--at teld-theme-color cursor-pointer mr-3 outline-on-focus"
            @click.capture.stop="onClickAt"
          >
            <At />
            <t-tooltip v-if="$q.platform.is.desktop">
              <div
                style="
                  width: 80px;
                  word-wrap: break-word;
                  text-wrap: wrap;
                "
                >{{ /*提及人员*/ qLangComment.atToMention }}</div
              >
            </t-tooltip>
          </div>

          <!-- 附件图标 -->
          <div
            class="teld-theme-color"
            :class="[
              isFileCountOverflow ? 'cursor-not-allowed' : 'cursor-pointer',
            ]"
            @click.capture.stop="toAddAttachment"
          >
            <Attachment />
            <t-tooltip v-if="$q.platform.is.desktop">
              <div
                style="width: 240px; word-wrap: break-word; text-wrap: wrap"
                >{{ attachmentTips }}</div
              >
            </t-tooltip>
          </div>
          <!-- 上传文件用的 input -->
          <input
            ref="fileInputRef"
            type="file"
            multiple
            style="opacity: 0; width: 0; height: 0"
            @change="onInputFileChange"
          />
        </div>

        <div class="right">
          <t-button
            color="primary"
            @click="onClickPublishComment"
            style="width: 60px; padding: 8px; margin: 0"
            padding="xs lg"
            size=""
          >
            {{ buttonText }}
            <t-inner-loading :showing="isInputLoading">
              <t-spinner size="20px" color="primary" />
            </t-inner-loading>
          </t-button>
        </div>
      </div>

      <!-- 粘贴图片时的 loading 遮罩，避免放在 inputRef 里造成回显时遮罩还在 -->
      <div v-if="isInputLoading" class="absolute inset-0 rounded">
        <t-inner-loading :showing="isInputLoading">
          <t-spinner size="20px" color="primary" />
        </t-inner-loading>
      </div>
    </div>

    <!-- absolute 定位浮动选人 -->
    <PersonnelSelectorPopup
      ref="popupRef"
      v-model:visible="personnelSelectorPopupVisible"
      :offset-parent="offsetParentElement"
      :keyword="queryUserName"
      @selected="onSelectPersonnel"
    ></PersonnelSelectorPopup>

    <!-- 大图查看 -->
    <t-image-preview
      ref="imagePreviewRef"
      v-model="currentImgSrc"
      :src-list="previewSrcList"
    ></t-image-preview>
  </div>
</template>

<script>
import { reactive } from 'vue';

// Components
import At from '../comment/components/svg-icons/at.vue';
import Attachment from '../comment/components/svg-icons/attachment.vue';
import Clear from '../comment/components/svg-icons/clear.vue';
import FileTypeIcon from '../comment/components/file-type-icon/index.vue';
import UserAvatar from '../comment/components/user-avatar/index.vue';
// import PersonnelSelectorPopup from '../comment/pc/personnel-selector-popup/index.vue';

// Utils
import {
  readAsBase64,
  isSupportedImageType,
  formatBytes,
  useTimeout,
  //
  hasAtInput,
  getAtUser,
  replaceAtUser,
  getMentionedUserIdList,
  getNotEditableContent,
  //
  disableKeyCodes,
} from '../comment/hooks';
import { apiUploadFile } from '../comment/hooks/api';
import {
  SUPPORTED_FILE_TYPES,
  ATTACHMENT_MAX_COUNT,
  SUBMIT_CONTENT_LIMIT,
} from '../comment/utils';

/**
 * 草稿输入框组件
 */
export default {
  name: 'TpDraftEditor',
  inject: [
    'qLangComment',
    'loginUserInfo',
    'getAttachmentMaxSize',
    'getContainerForPersonnelSelectorPopup',
    'updateRecentlyMentionedUsers',
    'emitEvent',
    //
    'inputMaxLength',
    'sendButtonText',
  ],
  props: {
    isComment: {
      type: Boolean,
      default: false,
    },
    /**透传visible用于点开时给editor赋焦点*/
    visible: {
      type: Boolean,
      default: false,
    },
    /**是否可独立窗口打开*/
    canBeIndependent: {
      type: Boolean,
      default: false,
    },
    showAvatar: {
      type: Boolean,
      default: false,
    },
    autoClearOnHidding: {
      type: Boolean,
      default: false,
    },
    placeholder: {
      type: String,
      default: '',
    },
    /**
     * 简易选人popup确定位置用的参照系元素
     * - 本组件直接父级时，使用 props 传参，优先，未传即是跨多层级
     * - 跨多个层级时，使用 provide/inject 方式获取参照系元素
     */
    offsetParent: {
      type: HTMLElement,
    },
  },
  components: {
    At,
    Attachment,
    Clear,
    FileTypeIcon,
    UserAvatar,
    PersonnelSelectorPopup,
  },
  watch: {
    visible(val) {
      if (val) {
        this.initFocus();
      } else {
        this.isInputEmpty = false;
        this.autoClearOnHidding && this.reset();
      }
    },
  },
  computed: {
    // loginUser() {
    //   return this.loginUserInfo && this.loginUserInfo();
    // },
    hasAttachments() {
      return !!this.fileList.length && this.fileList.some(n=>n.status ==='done');
    },
    isInputOverflow() {
      return this.draftWordLength > this.inputMaxLength;
    },
    isFileCountOverflow() {
      return this.fileList.length >= ATTACHMENT_MAX_COUNT;
    },
    attachmentTips() {
      // return `附件限制${ATTACHMENT_MAX_COUNT}个以内，支持如下文件类型：${SUPPORTED_FILE_TYPES}`;
      return this.qLangComment.funcAttachmentLimit([
        ATTACHMENT_MAX_COUNT,
        SUPPORTED_FILE_TYPES,
      ]);
    },
    buttonText() {
      const btnText = this.sendButtonText;
      return btnText && btnText.trim()
        ? btnText.trim()
        : this.isComment
          ? this.qLangComment.comment
          : this.qLangComment.reply;
    },
  },
  data() {
    // this.inputMaxLength = this.$commentOptions.inputMaxLength;
    // this.SUBMIT_CONTENT_LIMIT = SUBMIT_CONTENT_LIMIT;
    return {
      /**
       * {
       *    file: {},
       *    result: {},
       * }
       * file:文件对象（注意，纯input实现的跟原来vue2中upload组件的file对象不同，没有包装属性）
       * result: 文件上传后的结果对象
       */
      fileList: [],
      draftContent: '', // 要提交的富文本内容
      draftWordLength: 0, // 纯文本字数，包含空格
      trimmedContent: '', // 草稿缩略显示用文字内容
      isInputLoading: false,
      isInputEmpty: false,
      previewSrcList: [],
      currentImgSrc: '',
      personnelSelectorPopupVisible: false,
      queryUserName: '',
      /**定位快捷选人的位置用*/
      offsetParentElement: null,
    };
  },
  mounted() {
    this.bindEvents();
    setTimeout(() => {
      // 通过setTimeout确保上面绑定的focus事件会被执行
      this.initFocus();
    });
  },
  beforeDestroy() {
    const inputBox = this.$refs.draftEditorInputRef;
    if (inputBox) {
      inputBox.removeEventListener('focus', this.onFocus);
      inputBox.removeEventListener('blur', this.onBlur);
      inputBox.removeEventListener('input', this.onInput);
      inputBox.addEventListener('paste', this.onPaste);
    }
    document.body.removeEventListener('click', this.onClickBody);
  },
  methods: {
    isSupportedImageType,
    onClickBody(e) {
      const inputBox = this.$refs.draftEditorInputRef;
      const attachmentBox = this.$el.querySelector('.file-list-preview');
      if (!inputBox) return;
      // 图片预览中不做处理
      if (this.currentImgSrc) return;
      if (this.$el.contains(e.target)) {
        if (!inputBox.contains(e.target) && !attachmentBox.contains(e.target)) {
          this.initFocus();
        }
      } else {
        inputBox.classList.add('max-h-5');
        inputBox.classList.remove('min-h-5', 'max-h-36');
        inputBox.scrollTop = 0;
        this.personnelSelectorPopupVisible = false;
      }
    },
    bindEvents() {
      const inputBox = this.$refs.draftEditorInputRef;
      inputBox.addEventListener('focus', this.onFocus);
      inputBox.addEventListener('blur', this.onBlur);
      inputBox.addEventListener('input', this.onInput);
      inputBox.addEventListener('paste', this.onPaste);
      document.body.addEventListener('click', this.onClickBody);
    },
    initFocus() {
      const inputBox = this.$refs.draftEditorInputRef;
      const range = new Range();
      range.selectNodeContents(inputBox);
      range.collapse(false);
      const selection = getSelection();
      selection.removeAllRanges();
      selection.addRange(range);
      useTimeout(() => {
        inputBox.focus();
        inputBox.scrollTop = inputBox.scrollHeight;
      }, 300);
    },
    /**
     * 获取到焦点时的处理
     * 来源有三种，
     * - 1.初始化时自动设焦点 initFocus
     * - 2.点进输入框得到焦点
     * - 3.点评论框手动设焦点 initFocus
     * 其中1.和3.需要将内容滚动到末尾
     */
    onFocus(e) {
      // 补回失去焦点时去掉的类
      e.target.classList.remove('max-h-5');
      e.target.classList.add('min-h-5', 'max-h-36');
      useTimeout(() => {
        e.target.scrollIntoView({
          behavior: 'smooth',
          block: 'center',
          inline: 'start',
        });
      }, 100);
      //
      // console.log("onFocus", e);
      const inputWrapper = this.$refs.inputWrapperRef;
      inputWrapper.classList.add('border-theme-color');
    },
    onBlur(e) {
      const inputWrapper = this.$refs.inputWrapperRef;
      inputWrapper.classList.remove('border-theme-color');
    },
    onInput(e) {
      // Backspace删除时，阻止浏览器在删光内容后自动添加`<br>`的行为
      if (e.inputType === 'deleteContentBackward') {
        const innerHTML = this.$refs.draftEditorInputRef.innerHTML;
        if (innerHTML === '' || innerHTML.toLowerCase() == '<br>') {
          e.preventDefault();
          // e.stopPropagation();
          this.$refs.draftEditorInputRef.innerHTML = '';
        }
      }
      // 直接输入`@`和[Backspace,ArrowLeft,ArrowRight]等造成光标落到`@`时
      const lastChar = e.data || e.target.innerText.slice(-1);
      if (lastChar === '@') {
        this.handleAtInput();
      }

      // console.log("onInput", e);
      this.isInputEmpty = false;
      this.draftContent = e.target.innerHTML;
      this.trimmedContent = e.target.textContent.trim();
      this.draftWordLength = e.target.innerText.length;
    },
    async onPaste(e) {
      e.preventDefault();
      const clipboardData = (e.originalEvent || e).clipboardData;
      if (!clipboardData) {
        return;
      }

      const items = clipboardData.items;
      for (let index = 0; index < items.length; index++) {
        const item = items[index];
        console.log('start----------');
        console.log('onPaste-item', item);
        if (item.kind === 'file' && isSupportedImageType(item.type)) {
          e.preventDefault();
          // 粘贴图像
          console.log(`粘贴图像`);
          const file = item.getAsFile();
          this.handlePasteImage(file);
        }
        // else if(item.type ==='text/plain') {
        //   item.getAsString((str) => {
        //     console.log('text/plain', str);
        //   })
        // } else if (item.type === 'text/html') {
        //   item.getAsString((str) => {
        //     console.log('text/html',str);
        //   })
        // }
        console.log('end----------\n');
      }

      const pastedText = clipboardData.getData('text/plain');
      if (pastedText) {
        console.log('clipboardData text/plain', pastedText);
        document.execCommand('insertText', false, pastedText);
      }
    },
    async handlePasteImage(file) {
      this.isInputLoading = true;
      const [strWithType] = await readAsBase64(file);
      try {
        // const res = await this.$utils.sgApi.getDataAsync({
        //   url: this.$utils.common.getServiceUrl('CMTS-UploadAttachment'),
        //   data: {
        //     param: JSON.stringify({
        //       FileName: file.name,
        //       FileContent: strWithType,
        //     }),
        //   },
        // });
        const res = await apiUploadFile({
          fileName: file.name,
          fileContent: strWithType,
        });
        console.log('PC upload file', res);

        if (!res || res.state !== '1' || !res.data) {
          // this.$TP.$message.error(`图像上传失败(${data.errmsg})`);
          this.$q.notify({
            message: this.qLangComment.funcImageUploadFailed(res.errmsg),
            // icon: 'error_outline',
            type: 'negative',
            textColor: 'white',
            position: 'top',
            timeout: 1000,
            attrs: {
              // 为通知的 DOM 本身设置属性:
              'comment-notification': 'negative', // 这是为了做精准选择器调样式用的
            },
          });
          return;
        }

        const { AttachmentUrl } = res.data;
        const img = document.createElement('img');
        img.setAttribute('data-pasted-image', true);
        img.src = AttachmentUrl;
        const inputBox = this.$refs.draftEditorInputRef;
        inputBox.appendChild(img);
        this.isInputEmpty = false;
        setTimeout(() => {
          // 需要在关掉 loading 之后，再获取输入框的内容
          this.draftContent = inputBox.innerHTML;
        }, 200);
      } catch (error) {
        // this.$TP.$message.error(`图像上传失败`);
        this.$q.notify({
          message: this.qLangComment.funcImageUploadFailed(),
          // icon: 'error_outline',
          type: 'negative',
          textColor: 'white',
          position: 'top',
          timeout: 1000,
          attrs: {
            // 为通知的 DOM 本身设置属性:
            'comment-notification': 'negative', // 这是为了做精准选择器调样式用的
          },
        });
        console.log(error);
      } finally {
        this.isInputLoading = false;
        this.initFocus();
      }
    },
    /**
     * 点击发布评论/回复按钮的处理
     *
     * @description
     *  - 关于提交内容非空检查
     *    - 无附件时，不允许空提交，仅空格、仅回车、仅空格加回车都不行
     *    - 有附件时，允许空提交，仅空格、仅回车、仅空格加回车时，内容置换为空文字
     */
    onClickPublishComment() {
      if (this.isInputLoading) return;

      // 检查有没有粘贴图片
      const inputBox = this.$refs.draftEditorInputRef;
      const pastedImages = inputBox.querySelectorAll('[data-pasted-image]');
      const hasPastedImage = !!pastedImages.length;

      // 没有有效内容，且没有附件，且没有粘贴图片时
      this.isInputEmpty = false;
      if (!this.trimmedContent && !this.hasAttachments && !hasPastedImage) {
        // this.$TP.$message.warning('请检查输入内容');
        this.$q.notify({
          // message: '请检查输入内容',
          message: this.qLangComment.msgCheckInput,
          icon: 'warning_amber',
          type: 'warning',
          textColor: 'white',
          position: 'top',
          timeout: 1000,
          attrs: {
            // 为通知的 DOM 本身设置属性:
            'comment-notification': 'warning', // 这是为了做精准选择器调样式用的
          },
        });
        this.isInputEmpty = true;
        this.initFocus();
        return;
      }

      if (this.isInputOverflow) {
        // this.$TP.$message.warning('内容超出文字数上限');
        this.$q.notify({
          // message: `内容超出文字数上限`,
          message: this.qLangComment.inputTooLong,
          // icon: 'error_outline',
          type: 'warning',
          textColor: 'white',
          position: 'top',
          timeout: 1000,
          attrs: {
            // 为通知的 DOM 本身设置属性:
            'comment-notification': 'warning', // 这是为了做精准选择器调样式用的
          },
        });
        this.initFocus();
        return;
      }

      if (this.draftContent.length > SUBMIT_CONTENT_LIMIT) {
        // this.$TP.$message.warning(
        //   `内容超出可提交文字数上限(${this.draftContent.length}/${SUBMIT_CONTENT_LIMIT})`,
        // );
        this.$q.notify({
          message: `${errSubmitContentTooLong}(${this.draftContent.length}/${SUBMIT_CONTENT_LIMIT})`,
          // icon: 'error_outline',
          type: 'warning',
          textColor: 'white',
          position: 'top',
          timeout: 1000,
          attrs: {
            // 为通知的 DOM 本身设置属性:
            'comment-notification': 'warning', // 这是为了做精准选择器调样式用的
          },
        });
        this.initFocus();
        return;
      }

      this.isInputLoading = true;
      const userIdList = getMentionedUserIdList(inputBox);
      const content =
        this.trimmedContent || hasPastedImage
          ? encodeURIComponent(getNotEditableContent(inputBox))
          : '';
      this.$emit('comment', {
        content,
        fileList: this.fileList.filter(n => n.result && n.status === 'done'),
        // fileList: [
        //   { file:{ name:'微信图片_20240226154537.png'},
        //   result: {
        //   "CommentID": null,
        //   "AttachmentType": 0,
        //   "AttachmentUrl": "http://resource.teld3.xyz/teldimage/160/c3845dc0a1b245838ee67bfc272d443d.png",
        //   "UploadTime": "2024-02-27T11:34:04.0801216+08:00",
        //   "FileName": "微信图片_20240226154537.png",
        //   "ID": "0ecb67a7-3179-4cb0-a20d-fa9eb8354000",
        //   "Creator": null,
        //   "CreateTime": "0001-01-01T00:00:00",
        //   "LastModifier": null,
        //   "LastModifyTime": "0001-01-01T00:00:00"
        // }}],
        userIdList,
        onSucceed: (args = {}) => {
          // this.$TP.$message.success({ message: '发送成功', offset: 80 });
          this.$q.notify({
            // message: `发送成功`,
            message: this.qLangComment.msgSendSuccess,
            // icon: '',
            type: 'positive',
            textColor: 'white',
            position: 'top',
            timeout: 1000,
            attrs: {
              // 为通知的 DOM 本身设置属性:
              'comment-notification': 'positive', // 这是为了做精准选择器调样式用的
            },
          });
          this.reset(args);
          this.emitEvent('comment', {
            content,
            mentionedUsers: userIdList,
          });
        },
        onFail: (arags = {}) => {
          // this.$TP.$message({ message: '发送失败', offset: 80 });
          this.$q.notify({
            // message: `发送失败`,
            message: this.qLangComment.msgSendFail,
            // icon: '',
            type: 'negative',
            textColor: 'white',
            position: 'top',
            timeout: 1000,
            attrs: {
              // 为通知的 DOM 本身设置属性:
              'comment-notification': 'negative', // 这是为了做精准选择器调样式用的
            },
          });
          this.isInputLoading = false;
        },
      });
    },
    reset({ focus } = {}) {
      this.isInputLoading = false;
      // 清空内容
      const inputBox = this.$refs.draftEditorInputRef;
      if (inputBox) {
        inputBox.innerHTML = '';
      }
      this.draftContent = '';
      this.trimmedContent = '';
      this.draftWordLength = 0;
      this.fileList = [];
      // PC端的tp-upload自定义上传需要手动清空文件
      // this.$refs.uploaderRef.clearFiles();

      // 主列表中添加评论后继续设回焦点
      if (focus) {
        setTimeout(() => {
          this.$refs.draftEditorInputRef.focus();
        }, 500);
      }
    },
    /**
     * -------------------------
     * 附件/文件上传相关
     * -------------------------
     * 注：没有可用组件，使用原生 input 元素实现
     */
    toAddAttachment() {
      this.$refs.fileInputRef.click();
    },
    onInputFileChange(e) {
      const files = Array.from(e.target.files);
      for(let i = 0; i < files.length; i++) {
        this.onFileChange(files[i]);
      }
      e.target.value = '';
    },
    /** 选取文件后的处理，检查通过后直接上传 */
    async onFileChange(file) {
      console.log('file', file);

      // check file size
      const sizeBytes = this.getAttachmentMaxSize();
      if (file.size > sizeBytes) {
        const sizeFormat = formatBytes(sizeBytes);
        // this.$TP.$message.warning(`请上传 ${sizeFormat} 以内的附件`);
        this.$q.notify({
          // message: `请上传 ${sizeFormat} 以内的附件`,
          message: this.qLangComment.funcFileSizeLimit(sizeFormat),
          // icon: '',
          type: 'warning',
          textColor: 'white',
          position: 'top',
          timeout: 1000,
          attrs: {
            // 为通知的 DOM 本身设置属性:
            'comment-notification': 'warning', // 这是为了做精准选择器调样式用的
          },
        });
        return false;
      }

      // check file type
      const fileExt = file.name.split('.').pop();
      const checkFileType = SUPPORTED_FILE_TYPES.split(',').includes(fileExt);
      if (!checkFileType) {
        // this.$TP.$message.info(`不支持的文件类型(${file.name})`);
        this.$q.notify({
          // message: `不支持的文件类型(${file.name})`,
          message: this.qLangComment.funcFileTypeNotSupport(file.name),
          // icon: '',
          type: 'info',
          textColor: 'white',
          position: 'top',
          timeout: 1000,
          attrs: {
            // 为通知的 DOM 本身设置属性:
            'comment-notification': 'info', // 这是为了做精准选择器调样式用的
          },
        });
        return;
      }

      this.uploadFile(file);
    },
    /**
     * 上传文件的处理
     * 通过 file.status 控制 loading 状态
     */
    async uploadFile(file) {
      // file.status = 'ready'; // 文件状态，原el-upload包装属性
      const [strWithType, base64Str] = await readAsBase64(file);
      if (!base64Str) {
        // this.$TP.$message.warning(`文件内容为空(${file.name})`);
        this.$q.notify({
          // message: `文件内容为空(${file.name})`,
          message: this.qLangComment.funcEmptyFile(file.name),
          // icon: '',
          type: 'warning',
          textColor: 'white',
          position: 'top',
          timeout: 1000,
          attrs: {
            // 为通知的 DOM 本身设置属性:
            'comment-notification': 'warning', // 这是为了做精准选择器调样式用的
          },
        });
        return;
      }

      // check file duplicated
      if (this.fileList.some(n => n.content === strWithType)) {
        // this.$TP.$message.info(`文件重复上传(${file.name})`);
        this.$q.notify({
          // message: `文件重复上传(${file.name})`,
          message: this.qLangComment.funcFileDuplicated(file.name),
          // icon: '',
          type: 'info',
          textColor: 'white',
          position: 'top',
          timeout: 1000,
          attrs: {
            // 为通知的 DOM 本身设置属性:
            'comment-notification': 'info', // 这是为了做精准选择器调样式用的
          },
        });
        return;
      }

      // let retFlag = false;
      const onFail = () => {
        // file.status = 'failed';
        // this.$TP.$message.error(`上传失败(${file.name})`);
        this.$q.notify({
          // message: `上传失败(${file.name})`,
          message: this.qLangComment.funcUploadFailed(file.name),
          // icon: '',
          type: 'negative',
          textColor: 'white',
          position: 'top',
          timeout: 1000,
          attrs: {
            // 为通知的 DOM 本身设置属性:
            'comment-notification': 'negative', // 这是为了做精准选择器调样式用的
          },
        });
        // 上传失败后上传组件中去掉该文件
        this.fileList = this.fileList.filter(n => n.uid !== newFile.uid);
      };
      const newFile = reactive({
        file,
        result: {},
        uid: Date.now(),
        status: 'ready',
        content: strWithType,
      });
      this.fileList.push(newFile);

      try {
        // const res = await this.$utils.sgApi.getDataAsync({
        //   url: this.$utils.common.getServiceUrl('CMTS-UploadAttachment'),
        //   data: {
        //     param: JSON.stringify({
        //       FileName: file.name,
        //       FileContent: strWithType,
        //     }),
        //   },
        // });
        const res = await apiUploadFile({
          fileName: file.name,
          fileContent: strWithType,
        });
        console.log('PC upload file', res);

        if (!res || res.state !== '1') {
          onFail();
          return;
        }

        // retFlag = true;
        // file.status = 'done';
        newFile.status = 'done';
        newFile.result = res.data;
      } catch (error) {
        onFail();
      }
    },
    handleRemoveFile(index) {
      this.fileList.splice(index, 1);
    },
    /**
     * 附件/文件上传相关
     * -------------------------
     */
    /** @deprecated */
    onImageLoad(e) {
      this.$refs.imgPreviewRef.$el.querySelector('img').click();
    },
    onClickContent(e) {
      const el = e.target;
      if (el.nodeName === 'IMG' && el.currentSrc) {
        this.currentImgSrc = el.currentSrc;
      }
    },
    /**
     * 附件图片预览
     * @param fileItem
     * @deprecated 无预览组件，暂时不可用
     */
    onClickAttachmentItem(fileItem) {
      console.log('onClickAttachment', fileItem);
      if (fileItem && fileItem.file) {
        this.currentImgSrc = '';
        this.previewSrcList = [];
        this.$nextTick(() => {
          const imgUrl = fileItem.result.AttachmentUrl;
          this.previewSrcList = [imgUrl];
          this.currentImgSrc = imgUrl;
        });
      }
    },
    /**
     * 附件图片预览
     * @param fileItem
     * @description
     * quasar中没有图片预览组件，无法直接实现
     * PC 端可弹新tab，移动端 TODO
     */
    onClickAttachmentPreview(fileItem) {
      if (!fileItem.result || !fileItem.result.AttachmentUrl) {
        return;
      }
      const imgUrl = fileItem.result.AttachmentUrl;
      const previwer = this.$refs.imagePreviewRef;
      if (previwer) {
        previwer.open(imgUrl);
      }
    },
    /**
     * -------------------------
     * 快捷选人相关
     */
    onKeyDown(e) {
      // 当弹出弹窗时，屏蔽输入框的部分键盘操作
      if (this.personnelSelectorPopupVisible) {
        // 需屏蔽：上下键回车键和 Esc，其它正常响应
        const keyCodes = ['ArrowUp', 'ArrowDown', 'Enter', 'Escape'];
        disableKeyCodes(e, keyCodes);
      }
    },
    onKeyup(e) {
      const popupHandedKeys = ['Enter', 'ArrowUp', 'ArrowDown'];
      if (
        this.personnelSelectorPopupVisible &&
        popupHandedKeys.includes(e.key)
      ) {
        e.preventDefault();
        e.stopPropagation();
        return;
      }
      if (e.key === 'Escape') {
        if (this.personnelSelectorPopupVisible) {
          e.preventDefault();
          e.stopPropagation();
          this.personnelSelectorPopupVisible = false;
        }
        return;
      }

      // 直接输入`@`的场景，在`input`事件中以进行处理
      if (e.key === '@') {
        return;
      }

      this.checkInputOfAt();
    },
    checkInputOfAt() {
      if (hasAtInput()) {
        this.handleAtInput();
      } else {
        this.personnelSelectorPopupVisible = false;
      }
    },
    handleAtInput() {
      this.queryUserName = getAtUser();
      // 动态确定参照系父元素
      this.offsetParentElement =
        this.offsetParent ||
        this.getContainerForPersonnelSelectorPopup(this.isComment);
      this.personnelSelectorPopupVisible = true;
    },
    /**
     * 鼠标点击致使焦点发生变化，当遇到`@`时弹出简易选人框
     * 点到转换后的`@人员`时不做处理
     * @param {MouseEvent} e
     */
    onMouseUp(e) {
      if (e.target.dataset.userId) return;
      this.checkInputOfAt();
    },
    /**快捷选人选择后的处理：创建回显块替换输入的文本*/
    onSelectPersonnel(item) {
      replaceAtUser([item]);
      this.$refs.draftEditorInputRef.dispatchEvent(new Event('input'));
      this.personnelSelectorPopupVisible = false;
      this.updateRecentlyMentionedUsers(item);
    },
    /**点at按钮弹出快捷选人*/
    onClickAt() {
      const inputBox = this.$refs.draftEditorInputRef;
      inputBox.focus();
      const txtAt = new Text('@');
      inputBox.appendChild(txtAt);
      inputBox.dispatchEvent(new Event('input'));

      const selection = getSelection();
      selection.removeAllRanges();
      const range = new Range();
      range.selectNode(txtAt);
      selection.addRange(range);
      range.collapse(false);
      // 点按钮时手动插入一个`@`所以固定的 offset=1
      selection.collapse(txtAt, 1);
      this.handleAtInput();
    },
    /**
     * 快捷选人相关
     * -------------------------
     */
  },
};
</script>

<style lang="scss">
.draft-editor {
  &.with-shadow {
    box-shadow: 0 0px 2px 1px #e3e3e3;
  }

  .file-list-preview {
    .file-item:hover {
      .hover-to-show {
        display: flex !important;
      }
    }
  }

  .border-theme-color {
    border-color: var(
      --t-color
    ) !important; // 这里需要优先级比 hover 高，hover是加深一点的灰色
  }
  .draft-editor--input {
    transition: all 400ms;

    img {
      max-width: 120px !important;
      height: 80px !important;
      object-fit: contain !important;
      cursor: pointer !important;
    }
  }

  .draft-editor--input::-webkit-scrollbar {
    width: 0;
  }
}
</style>
